# release burndown plugin

import pkg_resources
import locale
import re
import datetime
import time

from urllib import quote, urlencode
from trac.wiki.macros import WikiMacroBase
from trac.core import *
from trac.ticket.model import Milestone, Ticket, TicketSystem
from trac.web.chrome import INavigationContributor
from trac.web.main import IRequestHandler
from trac.env import IEnvironmentSetupParticipant
from trac.util import escape, Markup
from trac.perm import IPermissionRequestor
from trac.web.chrome import ITemplateProvider, Chrome
from trac.db import Column, DatabaseManager, Table
from trac.util.datefmt import parse_date, utc, to_timestamp, to_datetime, \
                              get_date_format_hint, get_datetime_format_hint, \
                              format_date, format_datetime

# Get cursor from table
def _schema_to_sql(env, db, table):
    connector, _ = DatabaseManager(env)._get_connector()
    return connector.to_sql(table)

def has_permission(req, permission_name):
    return req.perm.has_permission('RELEASE_BURNDOWN_ADMIN') or req.perm.has_permission(permission_name)

def audit_action(req, release_name, env, action):
    database = env.get_db_cnx()
    cursor = database.cursor()

    cursor.execute("INSERT INTO releaseburndown_audit (release_name,action,accountable,action_date) values (%s,%s,%s,%s)",
        (release_name, action, req.authname, to_timestamp(datetime.datetime.now(utc))))

    database.commit()
    
# Class for plugin in trac
class ReleaseBurndownPlugin(Component):
    implements(INavigationContributor, IRequestHandler, ITemplateProvider, IEnvironmentSetupParticipant, IPermissionRequestor)

    #IPermissionRequestor methods
    def get_permission_actions(self):
       return ['RELEASE_BURNDOWN_ADMIN', 'RELEASE_BURNDOWN_VIEW', 'RELEASE_BURNDOWN_DELETE', 'RELEASE_BURNDOWN_MODIFY', 'RELEASE_BURNDOWN_CREATE']
       
    # INavigationContributor methods
    def get_active_navigation_item(self, req):
        return 'releaseburndown'

    def get_navigation_items(self, req):
        if(has_permission(req, 'RELEASE_BURNDOWN_VIEW')):
            yield 'mainnav', 'releaseburndown', Markup('<a href="%s">Release Burndown</a>' % (
                    self.env.href.releaseburndown() ) )

    # IRequestHandler methods
    def match_request(self, req):
        return req.path_info == '/releaseburndown'

    # Process Request
    def process_request(self, req):
        self.log.debug("process_request init")
        
        if(not has_permission(req, 'RELEASE_BURNDOWN_VIEW')):
            raise TracError('Permission denied to view Release Burndown')
            
        database = self.env.get_db_cnx()
        cursor = database.cursor()

        #req.redirect(self.env.href.admin(cat, page))
        #req.method == 'POST'
        #Release values
        release_name = req.args.get('release_name')
        points = req.args.get('points')
        velocity = req.args.get('velocity')
        init_date = req.args.get('init_date')
        working_days = req.args.get('working_days')
        act = req.args.get('act')
        id = req.args.get('id')
        init_date = init_date and parse_date(init_date, tzinfo=req.tz) or 0
        release_status = req.args.get('release_status')
        
        #Sprint values
        sprint_points = req.args.get('sprint_points')
        added_points = req.args.get('added_points') 
        removed_points = req.args.get('removed_points')
        sprint_id = req.args.get('sprint_id')
        due_date = req.args.get('due_date')
        due_date = due_date and parse_date(due_date, tzinfo=req.tz) or 0
        
        errors = []        
        refresh = True
        
        if act == 'add':
            self.log.debug("act add")
            
            if(has_permission(req, 'RELEASE_BURNDOWN_CREATE')):
                cursor.execute("INSERT INTO releaseburndown (release_name,points,velocity,init_date,working_days, closed) values (%s,%s,%s,%s,%s,0)",
                           (release_name,points,velocity,to_timestamp(init_date),working_days))
                           
                database.commit()

                audit_action(req, release_name, self.env, 'CREATE RELEASE')
            else:
                raise TracError('Permission denied to Create Release Burndown');
        elif act == 'view_audit':
            if(not req.perm.has_permission('RELEASE_BURNDOWN_ADMIN')):
                raise TracError('Permission denied to view Audit Log');

            audit = []
            cursor.execute("SELECT release_name,action,accountable,action_date FROM releaseburndown_audit ORDER by action_date DESC")

            for release_name,action,accountable,action_date in cursor:
                audit.append({'release_name': release_name, 'action': action, 'accountable': accountable, 'action_date': format_datetime(action_date, 'iso8601', req.tz)})
                if(len(audit) > 1000):
                    break
                
            return 'releaseburndown_audit.html', {'audits':audit}, None
        elif act == 'edit':
            self.log.debug("act edit")

            if(has_permission(req, 'RELEASE_BURNDOWN_MODIFY')):
                cursor.execute("UPDATE releaseburndown SET release_name = %s, points = %s, velocity = %s, init_date = %s, working_days = %s, closed = %s WHERE id = %s", (release_name, points, velocity, to_timestamp(init_date), working_days, release_status, id))
                database.commit()
                
                audit_action(req, release_name, self.env, 'EDIT RELEASE')
            else:
                raise TracError('Permission denied to modify Release Burndown');
        elif act == 'add_sprint':
            self.log.debug("act add_sprint")

            if(has_permission(req, 'RELEASE_BURNDOWN_MODIFY')):
                cursor.execute("INSERT INTO release_sprint_velocity (release_id, points, due_date, added_points, removed_points) values (%s,%s,%s,%s,%s)",
                                (id, sprint_points, to_timestamp(due_date), added_points, removed_points))
                database.commit()
                audit_action(req, release_name, self.env, 'ADD SPRINT')
            else:
                raise TracError('Permission denied to modify Release Burndown');
        elif act == 'remove_sprint':
            self.log.debug("act remove_sprint")
            
            if(has_permission(req, 'RELEASE_BURNDOWN_MODIFY')):
                cursor.execute("DELETE FROM release_sprint_velocity WHERE id = %s", (sprint_id))
                database.commit()
                audit_action(req, release_name, self.env, 'REMOVE SPRINT')
            else:
                raise TracError('Permission denied to modify Release Burndown');
        elif act == 'edit_sprint':
            self.log.debug("act edit_sprint")
            
            if(has_permission(req, 'RELEASE_BURNDOWN_MODIFY')):
                cursor.execute("UPDATE release_sprint_velocity SET points = %s, due_date = %s, added_points = %s, removed_points = %s WHERE id = %s",
                                (sprint_points, to_timestamp(due_date), added_points, removed_points, sprint_id))
                database.commit()
                audit_action(req, release_name, self.env, 'EDIT SPRINT')
            else:
                raise TracError('Permission denied to modify Release Burndown');
        elif act == 'remove_release':
            self.log.debug("act remove_release")

            if(has_permission(req, 'RELEASE_BURNDOWN_DELETE')):
                cursor.execute("DELETE FROM release_sprint_velocity WHERE release_id = %s", (id))
                cursor.execute("DELETE FROM releaseburndown WHERE id = %s", (id))
                database.commit()
                audit_action(req, release_name, self.env, 'REMOVE RELEASE')
            else:
                raise TracError('Permission denied to delete Release Burndown');
        else:
            refresh = False

        releases = []
        data = {
            'releases': releases,
            'date_hint': get_date_format_hint(),
            'refresh': refresh,
            'permissions': {
                'RELEASE_BURNDOWN_ADMIN':req.perm.has_permission('RELEASE_BURNDOWN_ADMIN'),
                'RELEASE_BURNDOWN_VIEW':has_permission(req, 'RELEASE_BURNDOWN_VIEW'),
                'RELEASE_BURNDOWN_DELETE': has_permission(req, 'RELEASE_BURNDOWN_DELETE'),
                'RELEASE_BURNDOWN_MODIFY': has_permission(req, 'RELEASE_BURNDOWN_MODIFY'),
                'RELEASE_BURNDOWN_CREATE': has_permission(req, 'RELEASE_BURNDOWN_CREATE')
            },
            'user': req.authname
        }
        cursor.execute("SELECT id,release_name,points,velocity,init_date,working_days,closed FROM releaseburndown")

        for id,release_name,points,velocity,init_date,working_days,closed in cursor:
            releases.append({'id': id, 'name': release_name, 'points': points, 'velocity': velocity, 'init_date':init_date, 'working_days': working_days, 'closed': closed})

        for release in releases:
            sprints = []
            release['sprints'] = sprints

            cursor.execute("SELECT id, release_id, points, due_date, added_points, removed_points FROM release_sprint_velocity WHERE release_id = %s ORDER BY due_date", (str(release['id'])))
            for id, release_id, points, due_date, added_points, removed_points in cursor:
                sprints.append({'id': id, 'release_id': release_id, 'points': points, 'due_date': due_date, 'added_points': added_points, 'removed_points': removed_points})

        return 'releaseburndown.html', data, None

    # ITemplateProvider methods
    def get_htdocs_dirs(self):
        return [('releaseburndown',pkg_resources.resource_filename('releaseburndown', 'htdocs'))]

    def get_templates_dirs(self):
        return [pkg_resources.resource_filename('releaseburndown', 'templates')]

    def upgrade_environment(self, db):
        self.log.debug('upgrade_environment')
        cursor = db.cursor()

        cursor.execute("SELECT value FROM system WHERE name='releaseburndown_version'")
        row = cursor.fetchone()

        if not row:
            cursor.execute("INSERT INTO system VALUES ('releaseburndown_version','1')")

            table_release = Table('releaseburndown', key='id')[
                Column('id', auto_increment=True),
                Column('release_name'),
                Column('points', type='int'),
                Column('velocity', type='int'),
                Column('init_date', type='int'),
                Column('working_days', type='int'),
                Column('closed', type='int')
            ]

            for stmt in _schema_to_sql(self.env, db, table_release):
                cursor.execute(stmt)

            table_sprints = Table('release_sprint_velocity', key='id')[
                Column('id', auto_increment=True),
                Column('release_id', type='int'),
                Column('points', type='int'),
                Column('added_points', type='int'),
                Column('removed_points', type='int'),
                Column('due_date', type='int')
            ]

            for stmt in _schema_to_sql(self.env, db, table_sprints):
                cursor.execute(stmt)
                
            db.commit()
        
        if not row or int(row[0]) < 2:
            cursor.execute("UPDATE system SET value = '2' WHERE name = 'releaseburndown_version'")
        
            table_audit = Table('releaseburndown_audit', key='id')[
                Column('id', auto_increment=True),
                Column('release_name'),
                Column('action'),
                Column('accountable'),
                Column('action_date', type='int')
            ]

            for stmt in _schema_to_sql(self.env, db, table_audit):
                cursor.execute(stmt)
                
            db.commit()
        
    def environment_created(self):
        self.log.debug('environment_created')
        self.upgrade_environment(self.env.get_db_cnx())

    def environment_needs_upgrade(self, db):
        self.log.debug('environment_needs_upgrade')
        cursor = db.cursor()
        cursor.execute("SELECT value FROM system "
                       "WHERE name='releaseburndown_version'")
        try:
            row = cursor.fetchone()
            if not row or int(row[0]) < 2:
                return True;
        except:
            db.rollback()
            return True;